package pl.edu.agh.ki.neuralnetwork.builder;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import pl.edu.agh.ki.neuralnetwork.exceptions.NotEnoughLayersException;
import pl.edu.agh.ki.neuralnetwork.exceptions.NotEnoughNeuronsException;
import pl.edu.agh.ki.neuralnetwork.exceptions.OutOfRangeException;
import pl.edu.agh.ki.neuralnetwork.exceptions.ResultNotReadyException;
import pl.edu.agh.ki.neuralnetwork.exceptions.WrongInputLayerSizeException;
import pl.edu.agh.ki.neuralnetwork.layer.BPInnerLayer;
import pl.edu.agh.ki.neuralnetwork.layer.BPLastLayer;
import pl.edu.agh.ki.neuralnetwork.layer.Layer;
import pl.edu.agh.ki.neuralnetwork.layer.SimpleLayer;
import pl.edu.agh.ki.neuralnetwork.network.BPNeuralNetworkImpl;
import pl.edu.agh.ki.neuralnetwork.network.NeuralNetwork;
import pl.edu.agh.ki.neuralnetwork.neurons.Bias;
import pl.edu.agh.ki.neuralnetwork.neurons.InnerNeuron;
import pl.edu.agh.ki.neuralnetwork.neurons.InputNeuron;
import pl.edu.agh.ki.neuralnetwork.neurons.InputNeuronImpl;
import pl.edu.agh.ki.neuralnetwork.neurons.SigmoidalNeuron;

public class BPNetworkBuilder implements NeuralNetworkBuilder {
	private String path;
	private int layersCount;
	private int layersRowsCount[];
	private int layersColsCount[];
	private int learningIterations;
	private int learningSamplesCount;
	private List<List<Double>> learningInput;
	private int toRecognizeCount;
	private List<List<Double>> toRecognizeInput;
	private int initMode;
	private double minRand;
	private double maxRand;
	private List<List<Double>> patterns;
	private int biasOn;
	private List<Double>  learningSpeedList;
	private List<Double>  momentumList;
	private int learningCooficientCount;
	private int momentumCooficientCount;

	public int getLearningIterations() {
		return learningIterations;
	}

	public List<List<Double>> getPatterns() {
		return patterns;
	}

	public List<List<Double>> getLearningInput() {
		return learningInput;
	}

	public List<List<Double>> getToRecognizeInput() {
		return toRecognizeInput;
	}

	public BPNetworkBuilder(String path) {
		this.path = path;
	}

	/**
	 * function omits line begins with #
	 * 
	 * @param br
	 * @return
	 * @throws IOException
	 */
	private String getNextLine(BufferedReader br) throws IOException {
		String t;
		while ((t = br.readLine()).charAt(0) == '#')
			;
		return t;
	}

	public NeuralNetwork build() throws WrongInputLayerSizeException,
			NumberFormatException, IOException, NotEnoughLayersException,
			NotEnoughNeuronsException {

		// reading parameters

		BufferedReader br = new BufferedReader(new FileReader(new File(path)));
		//read learning speed cooficient count
		learningCooficientCount = Integer.parseInt(getNextLine(br));
		learningSpeedList = new ArrayList(learningCooficientCount);
		//read learning speed cooficient
		for(int i=0;i<learningCooficientCount;i++){
			learningSpeedList.add(Double.parseDouble(getNextLine(br)));
		}
		//read momentum cooficient count
		momentumCooficientCount = Integer.parseInt(getNextLine(br));
		momentumList = new ArrayList(learningCooficientCount);
		//read learning speed cooficient
		for(int i=0;i<momentumCooficientCount;i++){
			momentumList.add(Double.parseDouble(getNextLine(br)));
		}
		// read weights initiation mode
		initMode = Integer.parseInt(getNextLine(br));
		// read max Rand
		minRand = Double.parseDouble(getNextLine(br));
		// read min Rand
		maxRand = Double.parseDouble(getNextLine(br));
		// read layers count
		layersCount = Integer.parseInt(getNextLine(br));
		// read bias
		biasOn = Integer.parseInt(getNextLine(br));
		layersRowsCount = new int[layersCount];
		layersColsCount = new int[layersCount];
		// read layers size
		for (int i = 0; i < layersCount; i++) {
			layersRowsCount[i] = Integer.parseInt(getNextLine(br));
			layersColsCount[i] = Integer.parseInt(getNextLine(br));
		}

		// read learning iterations
		learningIterations = Integer.parseInt(getNextLine(br));
		// read learning input count
		learningSamplesCount = Integer.parseInt(getNextLine(br));
		// read learning inputs (size of 1 layer)
		learningInput = new ArrayList<List<Double>>();
		for (int j = 0; j < learningSamplesCount; j++) {
			learningInput.add(new ArrayList<Double>(layersRowsCount[0]
					* layersColsCount[0]));

			for (int i = 0; i < layersRowsCount[0]; i++) {
				String[] line = getNextLine(br).split(" ");
				for (String s : line) {
					learningInput.get(j).add(Double.parseDouble(s));
				}
			}
		}
		// read patterns
		patterns = new ArrayList<List<Double>>();
		for (int j = 0; j < learningSamplesCount; j++) {
			patterns.add(new ArrayList<Double>(layersRowsCount[layersCount - 1]
					* layersColsCount[layersCount - 1]));

			for (int i = 0; i < layersRowsCount[layersCount - 1]; i++) {
				String[] line = getNextLine(br).split(" ");
				for (String s : line) {
					patterns.get(j).add(Double.parseDouble(s));
				}
			}
		}
		// read to recognize count
		toRecognizeCount = Integer.parseInt(getNextLine(br));
		// read to recognize input
		toRecognizeInput = new ArrayList<List<Double>>();
		for (int j = 0; j < toRecognizeCount; j++) {
			toRecognizeInput.add(new ArrayList<Double>(layersRowsCount[0]
					* layersColsCount[0]));

			for (int i = 0; i < layersRowsCount[0]; i++) {
				String[] line = getNextLine(br).split(" ");
				for (String s : line) {
					toRecognizeInput.get(j).add(Double.parseDouble(s));
				}
			}
		}
		// end of reading parameters

		// creating input layer
		SimpleLayer<InputNeuron> inputLayer = new SimpleLayer<InputNeuron>();
		for (int i = 0; i < layersRowsCount[0] * layersColsCount[0]; i++) {
			inputLayer.add(new InputNeuronImpl());
		}

		// creating inner layers

		List<Layer<InnerNeuron>> innerLayers = new ArrayList<Layer<InnerNeuron>>(
				layersCount - 1);
		for (int k = 1; k < layersCount; k++) {
			Layer<InnerNeuron> innerLayer;
			Bias bias = new Bias();
			if (k == layersCount - 1) {// last layer
				innerLayer = new BPLastLayer();
			} else {
				innerLayer = new BPInnerLayer();
			}
			Random rand = new Random();
			for (int i = 0; i < layersRowsCount[k] * layersColsCount[k]; i++) {

				List<Double> weights = new ArrayList<Double>(
						layersRowsCount[k - 1] * layersColsCount[k - 1]);
				if (initMode == 0) {
					for (int j = 0; j < layersRowsCount[k - 1]
							* layersColsCount[k - 1]; j++)
						weights.add(0.0d);
				} else if (initMode == 1) {
					for (int j = 0; j < layersRowsCount[k - 1]
							* layersColsCount[k - 1]; j++) {
						double t = rand.nextDouble();
						t = (maxRand - minRand) * t + minRand;
						weights.add(t);
					}
				}
				SigmoidalNeuron neuron;
				if (k == 1) {
					neuron = new SigmoidalNeuron(inputLayer.getNeuronsList(),
							weights);
				} else {
					neuron = new SigmoidalNeuron(innerLayers.get(k - 2)
							.getNeuronsList(), weights);
				}
				if (biasOn == 1) {
					if (initMode == 0) {
						neuron.setBiasN(bias, 1.0);
					} else {
						double t = rand.nextDouble();
						t = (maxRand - minRand) * t + minRand;
						neuron.setBiasN(bias, t);
					}
				}
				innerLayer.add(neuron);
			}
			innerLayers.add(innerLayer);
		}
		BPNeuralNetworkImpl bpn = new BPNeuralNetworkImpl(inputLayer,
				innerLayers);
		bpn.setLearningSpeedList( learningSpeedList);
		bpn.setMomemntumList(momentumList);
		return bpn;
	}

	public static void main(String[] args) throws WrongInputLayerSizeException,
			NumberFormatException, IOException, NotEnoughLayersException,
			NotEnoughNeuronsException, ResultNotReadyException,
			OutOfRangeException {
		String fileName;
		if (args.length < 2)
			fileName = "bp.txt";
		else
			fileName = args[1];
		BPNetworkBuilder bpNetworkBuilder = new BPNetworkBuilder(fileName);
		NeuralNetwork bpn = bpNetworkBuilder.build();
		for (int i = 0; i < bpNetworkBuilder.getToRecognizeInput().size(); i++) {
			bpn.setInputSignals(bpNetworkBuilder.getToRecognizeInput().get(i)
					.toArray(new Double[1]));
			printMod(bpNetworkBuilder.getToRecognizeInput().get(i), 3);
			bpn.compute();
			System.out.println("outputs: ");
			for (Double d : bpn.getOutputSignals()) {
				System.out.printf("%10.2f", d);
			}
			System.out.println();
		}
		System.out.println("uczenie BP");
//		System.out.println("warstwy przed uczeniem: ");
//		for (int i = 1; i < bpn.getLayersNumber(); i++) {
//			System.out.println("warstwa nr " + i);
//			System.out.println(bpn.getLayer(i));
//		}

		for (int i = 0; i < bpNetworkBuilder.getLearningIterations(); i++) {
			for (int j = 0; j < bpNetworkBuilder.getLearningInput().size(); j++) {
				bpn.learn(bpNetworkBuilder.getLearningInput().get(j),
						bpNetworkBuilder.getPatterns().get(j), i);
				if (i == bpNetworkBuilder.getLearningIterations() - 1) {
					BPLastLayer bll = (BPLastLayer) bpn.getLayer(bpn
							.getLayersNumber() - 1);
					System.out
							.println("error"
									+ j
									+ " "
									+ bll.getRMS(bpNetworkBuilder.getPatterns()
											.get(j)));
				}
			}
		}

		System.out.println("warstwy po uczeniu: ");
		for (int i = 1; i < bpn.getLayersNumber(); i++) {
			System.out.println("warstwa nr " + i);
			System.out.println(bpn.getLayer(i));
		}

		System.out.println("koniec uczenia BP");
		for (int i = 0; i < bpNetworkBuilder.getToRecognizeInput().size(); i++) {
			bpn.setInputSignals(bpNetworkBuilder.getToRecognizeInput().get(i)
					.toArray(new Double[1]));
			printMod(bpNetworkBuilder.getToRecognizeInput().get(i), 3);
			bpn.compute();
			System.out.println("outputs: ");
			for (Double d : bpn.getOutputSignals()) {
				System.out.printf("%10.2f", d);
			}
			System.out.println();
		}
	}

	private static void printMod(List<Double> list, int mod) {
		StringBuilder sb = new StringBuilder();
		int k = 0;
		for (Double d : list) {
			if (k % mod == 0)
				sb.append("\n");
			sb.append(d + " ");
			k++;
		}
		System.out.println(sb);
	}

}
